﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NTiering.EventHandlers;
using NTiering.Mappers;
using NTiering.Models;
using NTiering.Search;
using NTiering.Validators;

namespace NTiering
{
    public class Service : IService
    {
        List<MapperWrapper> _mappers = new List<MapperWrapper>();
        List<ValidatorWrapper> _validators = new List<ValidatorWrapper>();
        List<EventWrapper> _events = new List<EventWrapper>();

        #region Mappers
        /// <summary>
        /// Adds a new default Mappeign 
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <returns></returns>
        public int AddMapper<Tfrom, Tto>()
            where Tfrom : class,IModel, new()
            where Tto : class,IModel, new()
        {
            return AddMapper(new DefaultMapper<Tfrom, Tto>());
        }

        /// <summary>
        /// Adds a new default Mappeign 
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <returns></returns>
        public int AddMapper<Tfrom, Tto>(IMapper<Tfrom, Tto> mapper)
            where Tfrom : class,IModel, new()
            where Tto : class,IModel, new()
        {
            _mappers.Add(new MapperWrapper
            {
                From = typeof(Tfrom),
                To = typeof(Tto),
                Mapper = new SimpleMapper<Tfrom, Tto>(mapper.Map)
            });
            return _mappers.Count;
        }
        #endregion


        #region Validators
        public int AddValidator<T>(Func<T,IService, string> validator)
            where T : class,IDataModel
        {
            return AddValidator(new DefaultValidator<T>(validator));
        }

        public int AddValidator<T>(IValidator<T> validator)
            where T : class,IDataModel
        {
            _validators.Add(new ValidatorWrapper { Validator = new SimpleValidator<T>(validator), Type = typeof(T) });
            return _validators.Count;
        }
        #endregion


        #region Event Handlers
        public int AddEventHandler(EventHandlerTriggers trigger, Action<IEventContext> eventHandler)
        {
            _events.Add(new EventWrapper { Trigger = trigger, EventHandler = eventHandler });
            return _events.Count;
        }

        public int AddEventHandler(EventHandlerTriggers trigger, IEventHandler handler)
        {
            return AddEventHandler(trigger, handler.OnEvent);
        } 
        #endregion


        #region CRUD
        /// <summary>
        /// If validation is passed , an event is raised (EventHandlerTriggers.OnTryCreatePass) with the mapped data type populated with data 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="uiModel"></param>
        /// <param name="errors"></param>
        /// <returns></returns>
        public bool TryCreate<T>(T uiModel, List<string> errors = null)
            where T : IUiModel
        {
            return DoCreateUpdateDelete(uiModel, errors, EventHandlerTriggers.OnTryCreatePass);
        }

        /// <summary>
        /// If validation is passed , an event is raised (EventHandlerTriggers.OnTryUpdatePass) with the mapped data type populated with data 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="uiModel"></param>
        /// <param name="errors"></param>
        /// <returns></returns>
        public bool TryUpdate<T>(T uiModel, List<string> errors = null)
            where T : IUiModel
        {
            return DoCreateUpdateDelete(uiModel, errors, EventHandlerTriggers.OnTryUpdatePass);
        }

        /// <summary>
        /// If validation is passed , an event is raised (EventHandlerTriggers.OnTryDeletePass) with the mapped data type populated with data 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="uiModel"></param>
        /// <param name="errors"></param>
        /// <returns></returns>
        public bool TryDelete<T>(T uiModel, List<string> errors = null)
            where T : IUiModel
        {
            return DoCreateUpdateDelete(uiModel, errors, EventHandlerTriggers.OnTryDeletePass);
        }

        /// <summary>
        /// Does the actual create / update / delete handling 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="uiModel"></param>
        /// <param name="errors"></param>
        /// <param name="onPassEventType"></param>
        /// <returns></returns>
        private bool DoCreateUpdateDelete<T>(T uiModel, List<string> errors, EventHandlerTriggers onPassEventType)
            where T : IUiModel
        {
            if (errors == null)
            {
                errors = new List<string>();
            }

            // map to data type 
            var dataModel = MapToModel<T>(uiModel);

            // validate 
            var rtn = Validate(dataModel, errors);

            if (rtn)
            {
                RaiseEvent(onPassEventType,
                    new CreateUpdateDeleteEventContext
                    {
                        SourceObject = dataModel                         
                    });
            }
            else
            {
                RaiseEvent(EventHandlerTriggers.OnValidationFail,
                    new ValidationFailEventContext
                    {
                        Errors = errors,
                        SourceObject = uiModel
                    });
            }

            return rtn;
        } 
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context"></param>
        public void SearchFor<T>(ISearchContext context)
            where T : IUiModel
        {
            var start = DateTime.Now;
            context.Service = this;
            RaiseEvent(EventHandlerTriggers.OnSearchRequest, context);
            context.Duration = (DateTime.Now - start); 
        }


        /// <summary>
        /// Used to propogate events 
        /// </summary>
        /// <param name="eventHandlerTriggers"></param>
        private void RaiseEvent(EventHandlerTriggers trigger, IEventContext context = null)
        {
            if (context != null)
            {
                context.SourceTrigger = trigger;
                context.Service = this; 
            }

            var eh = _events.Where(x => x.Trigger == trigger);
            foreach (var e in eh)
            {
                e.EventHandler(context);
            }

        }

        /// <summary>
        /// Validates a data object against supplied validators
        /// </summary>
        /// <param name="dataModel"></param>
        /// <param name="errors"></param>
        /// <returns></returns>
        private bool Validate(object dataModel, List<string> errors)
        {
            var dataModelType = dataModel.GetType();
            var validators = _validators.Where(x => x.Type == dataModelType);

            foreach (var v in validators)
            {
                var result = v.Validator.Validate(dataModel,this as IService);
                if (String.IsNullOrEmpty(result) == false)
                {
                    errors.Add(result);
                }
            }

            return errors.Any() == false;
        }

       

        /// <summary>
        /// Maps the object to the first registered type it can find, Throws an Exception if none are found
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <param name="uiModel"></param>
        /// <returns></returns>
        public object MapToModel<T>(T uiModel)
             where T :IModel
        {
            var m = _mappers.FirstOrDefault(x => x.From == typeof(T));
            if (m == null)
                throw new MapperNotFoundException("Mapper for type " + typeof(T).FullName + " not found");
            return m.Mapper.Map(uiModel);
 
        }
        
    }
}
